/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.machine;


/**
 * Utility class used by SimpleMemory to clarify algorithms.
 * Hopefully the use of this class makes the code easier to read. 
 *
 */
class Block {
	
	final static int HEADER_SIZE = 4;
	final static int HEADER_OFFSET = 0;
	
	
	/**
	 * 
	 */
	final static int MARK_FLAG_BIT = (1 << 30);
	final static int USE_FLAG_BIT = (1 << 31);
	final static int ARRAY_FLAG_BIT = (1 << 29);
	final static int TYPE_MASK = ~(7 << 29);

	
	int address;
	SimpleMemory memory;
	
	
	public Block(SimpleMemory memory, int address) {
		this.address = address;
		this.memory = memory;
	}
	
	
	public String toString() {
		StringBuilder result = new StringBuilder("Block[");
		result.append("address=").append(address);
		result.append(", free=").append(isFree());
		result.append(", marked=").append(isMarked());
		result.append(", array=").append(isArray());
		if (isFree()) {
			result.append(", dataSize=").append(getInfo());
		} else {
			result.append(", type=").append(getTypeDescriptor());
		}
		result.append("]");
		return result.toString();
	}
	
	
	int getHeader() {
		return memory.peekInt(address + HEADER_OFFSET);
	}
	
	
	void setHeader(int header) {
		memory.poke(address + HEADER_OFFSET, header);
	}


	public boolean isFree() {
		return getHeader() >= 0;
	}
	
	
	public int getDataAddress() {
		return address + HEADER_SIZE;
	}
	
	
	public void setFree(boolean free) {
		setBit(USE_FLAG_BIT, !free);
	}
	
	
	public boolean isMarked() {
		return (getHeader() & MARK_FLAG_BIT) == MARK_FLAG_BIT;
	}
	
	
	public void setMarked(boolean marked) {
		setBit(MARK_FLAG_BIT, marked);
	}
	
	
	public boolean isArray() {
		return (getHeader() & ARRAY_FLAG_BIT) == ARRAY_FLAG_BIT;
	}
	
	
	public void setArray(boolean array) {
		setBit(ARRAY_FLAG_BIT, array);
	}
	
	
	private void setBit(int flagBit, boolean value) {
		int header = getHeader();
		if (value) {
			header = header | flagBit;
		} else {
			header = header & ~flagBit;
		}
		setHeader(header);
	}
	
	
	int getInfo() {
		return getHeader() & TYPE_MASK;
	}
	
	
	void setInfo(int info) {
		int header = getHeader();
		header = header & ~TYPE_MASK;
		header = header | info;
		setHeader(header);
	}


	public int getTotalSize() {
		return getDataSize() + HEADER_SIZE;
	}
	
	
	public int getArraySize() {
		if (!isArray()) {
			throw new IllegalStateException("Tried to fetch array size of non-array block.");
		}
		return memory.peekInt(address + 4);
	}
	
	
	public int getDataSize() {
		if (isFree()) {
			return getInfo();
		} else if (isArray()) {
			return SimpleMemory.calculateSize(getTypeDescriptor(), getArraySize());
		} else {
			return memory.getTypeDescriptor(getInfo()).getSize();
		}
	}
	
	
	void allocate(TypeDescriptor type, int arraySize) {
		if (!isFree()) {
			throw new IllegalStateException("Block already allocated.");
		}
		int freeSize = getInfo();
		int neededSize = SimpleMemory.calculateSize(type, arraySize);
		if (neededSize > freeSize) {
			throw new IllegalStateException("Block not big enough.  Need " + neededSize + " but have " + freeSize + ".");
		}
		int remainingSpace = freeSize - neededSize;
		
		setFree(false);
		setArray(arraySize >= 0);
		setInfo(memory.getTypeIndex(type));
		
		if (arraySize >= 0) {
			memory.poke(address + 4, arraySize);
		}
		
		remainingSpace -= HEADER_SIZE;
		if (remainingSpace > 0) {
			memory.poke(address + HEADER_SIZE + neededSize, remainingSpace);
		}
	}
	
	
	
	public Block getNextBlock() {
		int nextAddress = address + getTotalSize();
		if (nextAddress >= memory.getSize()) {
			return null;
		}
		return new Block(memory, nextAddress);
	}
	
	
	public TypeDescriptor getTypeDescriptor() {
		if (isFree()) {
			throw new IllegalStateException("free blocks have no type");
		}
		return memory.getTypeDescriptor(getInfo());
	}
	
	
	public void free() {
		setMarked(false);
		setInfo(getDataSize());
		setFree(true);
		int max = address + getDataSize();
		for (int a = address + HEADER_SIZE; a < max; a += 4) {
			memory.poke(a, 0);
		}
	}
	
	
	public void mergeFreeWithNext() {
		Block next = getNextBlock();
		setInfo(getDataSize() + next.getDataSize() + HEADER_SIZE);
		memory.poke(next.address, 0);
	}

}
